package org.smart4j.framework.helper;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.smart4j.framework.annotation.Aspect;
import org.smart4j.framework.annotation.Service;
import org.smart4j.framework.annotation.Transaction;
import org.smart4j.framework.proxy.AspectProxy;
import org.smart4j.framework.proxy.Proxy;
import org.smart4j.framework.proxy.ProxyManagr;
import org.smart4j.framework.proxy.TransactionProxy;

import java.lang.annotation.Annotation;
import java.util.*;

/**
 * @author ：Anitaxiaofeng
 * @description：方法拦截助手类
 * @create 2017-12-16
 **/
public class AopHelper {
    //需要获取所有的目标类及其被拦截的切面类的实例，并通过ProxyManager.createProxy方法来创建代理对象最后将其放入BeanMap中

    private static final Logger LOGGER = LoggerFactory.getLogger(AopHelper.class);

    static{
        try{
            Map<Class<?>,Set<Class<?>>> proxyMap = createProxyMap();
            Map<Class<?>,List<Proxy>> targetMap = createTargetMap(proxyMap);
            for(Map.Entry<Class<?>,List<Proxy>> targetEntry:targetMap.entrySet()){
                Class<?> targetClass = targetEntry.getKey();
                List<Proxy> proxyList = targetEntry.getValue();
                Object proxy = ProxyManagr.createProxy(targetClass,proxyList);
                BeanHelper.setBean(targetClass,proxy); // Bean 中类对应对象，这里就是目标类对应这个代理对象
            }
        } catch(Exception e){
            LOGGER.error("aop failure",e);
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
    }


    /**
     * 获取代理类及其目标类集合的映射关系也就是被@Aspect注解的类和Controller类的映射关系，可能一个proxy类可以对应多个目标类
     * @return (eg:ControllerAspect,Controller)
     * @throws Throwable
     */
    private static Map<Class<?>,Set<Class<?>>> createProxyMap()throws Throwable{
        Map<Class<?>,Set<Class<?>>> proxyMap = new HashMap<Class<?>, Set<Class<?>>>();
        addAspectProxy(proxyMap);
        addTransactionProxy(proxyMap);
        return proxyMap;
    }

    private static void addAspectProxy(Map<Class<?>,Set<Class<?>>> proxyMap) throws Throwable{
        Set<Class<?>> proxyClassSet = ClassHelper.getClassSetBySuper(AspectProxy.class);
        for(Class<?> proxyClass:proxyClassSet){
            if(proxyClass.isAnnotationPresent(Aspect.class)){
                Aspect aspect = proxyClass.getAnnotation(Aspect.class);
                Set<Class<?>> targetClassSet = createTargetClassSet(aspect);
                proxyMap.put(proxyClass,targetClassSet);
            }
        }
    }

    private static void addTransactionProxy(Map<Class<?>,Set<Class<?>>> proxyMap)throws Throwable{
        Set<Class<?>> serviceClassSet = ClassHelper.getClassSetByAnnotation(Service.class);
        proxyMap.put(TransactionProxy.class,serviceClassSet);
    }

    /**
     * 返回的是Aspect(Controller.class)Controller的类，这是目标类
     * @param aspect
     * @return
     * @throws Throwable
     */
    private static Set<Class<?>> createTargetClassSet(Aspect aspect) throws Throwable{
        Set<Class<?>> targetClassSet = new HashSet<Class<?>>();
        Class<? extends Annotation> annotation = aspect.value();
        if(annotation!=null&&!annotation.equals(Aspect.class)){
            targetClassSet.addAll(ClassHelper.getClassSetByAnnotation(annotation));
        }
        return targetClassSet;
    }



    /**
     * 根据代理类与目标类集合的映射关系，来分析出目标类与代理对象列表之间的映射关系
     * @param proxyMap
     * @return
     * @throws Throwable
     */
    public static Map<Class<?>,List<Proxy>> createTargetMap(Map<Class<?>,Set<Class<?>>> proxyMap) throws Throwable{
        Map<Class<?>,List<Proxy>> targetMap = new HashMap<Class<?>, List<Proxy>>();
        for(Map.Entry<Class<?>,Set<Class<?>>> proxyEntry:proxyMap.entrySet()){
            Class<?> proxyClass = proxyEntry.getKey();
            Set<Class<?>> targetClassSet = proxyEntry.getValue();
            for(Class<?> targetClass : targetClassSet){
                Proxy proxy = (Proxy)proxyClass.newInstance();
                if(targetMap.containsKey(targetClass)){
                    targetMap.get(targetClass).add(proxy);
                }else{
                    List<Proxy> proxyList = new ArrayList<Proxy>();
                    proxyList.add(proxy);
                    targetMap.put(targetClass,proxyList);
                }
            }
        }
        return targetMap;
    }

}
